/*
   Copyright 2010 Johan Hilding

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */


package org.johanhil.flygtider.provider.impl;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.http.client.utils.URIUtils;
import org.johanhil.flygtider.provider.FlightInfo;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

import android.net.Uri;
import android.util.Log;

public class LFVWebsiteParser
{
	private URL trafficInfoUrl;
	private final int BIG_AIRPORT = 1;
	private final int SMALL_AIRPORT = 2;
	
	public LFVWebsiteParser(URL url)
	{
		trafficInfoUrl = url;
	}
	
	/**
	 * Rip out everything except the table and make this an InputStream
	 * @param url The URL for the flight lists.
	 * @return An InputStream that contains proper XML.
	 */
	private InputStream createInputStream(URL url)
	{
		final String START_TAG = "<tbody>";
		final String END_TAG = "</tbody>";
		
		String html = getHtml(url);
		
		if (html == null)
		    return null;
		
		int start = html.indexOf(START_TAG);
		int end = html.indexOf(END_TAG)+END_TAG.length(); // need this to be inclusive
		
		Log.i("substring", "start = " + start);
		Log.i("substring", "end = " + end);
	
		// let's remove everything outside the tbody tags to create valid xml
		// and make it an input stream.
		// we also replace &nbsp; with a space and all &'s with o's to make the DOM parser not bail on us.
		String finalizedHtml = html.substring(start, end).replaceAll("&nbsp;", " ").replaceAll("&", "o");
		return new ByteArrayInputStream(finalizedHtml.getBytes());
	}
	
	/**
	 * Just fetches the contents of <tt>url</tt>. There's a library (but not Apache Xyz since it's so big) for this I'm sure.
	 * @param url URL to the website.
	 * @return The HTML as a String.
	 */
	private String getHtml(URL url)
	{
		HttpURLConnection connection = null;
		BufferedReader reader = null;
		StringBuffer buffer = new StringBuffer();
		
		try
		{
			connection = (HttpURLConnection) url.openConnection();
		} 
		catch (IOException ioe) 
		{
			Log.e("getHtml", "Could not connect to " + url);
			// TODO show this to the user...
		}
		
		try
		{
			reader = new BufferedReader(new InputStreamReader((InputStream)connection.getContent()));

			// TODO solve this in a better way, jesus.
			String temp = null;
			do
            {
				temp = reader.readLine();
                buffer.append(temp);
            } while (temp != null);			
		}
		catch (IOException ioe)
		{
			Log.e("getHtml", "Error while reading the stream.");
			Log.e("getHtml", ioe.getMessage());
			return null;
			// TODO visa för användaren...
		}
		
		return buffer.toString();
	}
	
	/**
	 * Builds a Document for parsing.
	 * @return A document ready for parsing.
	 */
	private Document buildDocument()
	{
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder documentBuilder = null;
		Document document = null;
		
		try {
			documentBuilder = documentBuilderFactory.newDocumentBuilder();
		} catch (ParserConfigurationException e1) {
			// TODO what should I do here and why does it happen?
			e1.printStackTrace();
			Log.e("buildDocument", "Could not create a DocumentBuilder.");
		}
		
		Log.d("buildDocument", trafficInfoUrl.toString());
		InputStream in = createInputStream(trafficInfoUrl);
		
		if (in == null)
		    return null;
		
		try {
			document = documentBuilder.parse(in);
		} catch (IOException ioe)
		{
			Log.e("buildDocument", ioe.getMessage());
		} catch (SAXException e) { 
			// TODO alert the user that this will not be fixed until the app is updated and have them file a bug
			Log.e("buildDocument", "parsing fail, trasig xml");
			Log.e("buildDocument", e.getMessage());
		}
		
		return document;
	}
	
	public List<FlightInfo> getFlights(String firstAirport, boolean arriving)
	{
		List<FlightInfo> flights = new ArrayList<FlightInfo>();
		int airportType = -1;
		long pre = System.currentTimeMillis();
		Document document = buildDocument();
		Log.i("perf", "it took " + (System.currentTimeMillis()-pre) + " ms to fetch & build the document");
		
		if (document == null)
		{
		    Log.d("getFlights", "document null!");
		    return null;
		}
		
		// find the <tbody>
		Element tbody = (Element) document.getElementsByTagName("tbody").item(0);
		
		NodeList tableRows = tbody.getElementsByTagName("tr");
				
		// skip the first row since it's just headers.
		for (int i = 1; i < tableRows.getLength(); i++)
		{
			FlightInfo flight = new FlightInfo();
			Element currentRow = (Element) tableRows.item(i);
			NodeList tableCells = currentRow.getElementsByTagName("td");
			
			if (airportType == -1) airportType = getAirportType(tableCells.getLength());
			
			// the first 4 cells are independent on the airport type 
			Element dateCell = (Element) tableCells.item(0);
			Element timeCell = (Element) tableCells.item(1);
			Element secondAirportCell = (Element) tableCells.item(2);
			Element flightNrCell = (Element) tableCells.item(3);
			Element terminalCell = null;
			Element notesCell = null;
			Element airlineCell = null;
			
			if (airportType == BIG_AIRPORT)
			{
				terminalCell = (Element) tableCells.item(4);
				notesCell = (Element) tableCells.item(5);
				airlineCell = (Element) tableCells.item(6);
			}
			else if (airportType == SMALL_AIRPORT)
			{
				notesCell = (Element) tableCells.item(4);
				airlineCell = (Element) tableCells.item(5);
			}
			
			flight.setDate(getData(dateCell));
			flight.setTime(getData(timeCell));
			flight.setArriving(arriving);
			
			if (arriving)
			{
				flight.setArrivingAirport(firstAirport);
				flight.setDepartingAirport(getData(secondAirportCell));
			}
			else
			{
				flight.setArrivingAirport(getData(secondAirportCell));
				flight.setDepartingAirport(firstAirport);
			}
			
			if (airportType == BIG_AIRPORT)
			{
				flight.setTerminal(getData(terminalCell));
			}
			else
			{
				flight.setTerminal(null);
			}
			
			flight.setFlightNr(getData(flightNrCell));
			flight.setAirline(getData(airlineCell));
			flight.setNotes(getData(notesCell));
			
			flights.add(flight);
		}
		
		Log.d("flights_size", "flights is " + flights.size());
		
		return flights;
	}
	
	/**
	 * Fetches the data from the (td) element e.
	 * @param e A TableData element.
	 * @return The text contained in e.
	 */
	private static String getData(Element e)
	{
		Text t = (Text) e.getChildNodes().item(0); 
		if (t == null) return "";
		return t.getData();
	}
	
	/**
	 * Some airports are big (contain multiple terminals) and some are small.
	 * @param numberOfColumns The number of columns, which is used to determine the size of the airport.
	 * @return Either LFVWebsiteParser.BIG_AIRPORT or LFVWebsiteParser.SMALL_AIRPORT
	 */
	private int getAirportType(int numberOfColumns)
	{	    
		if (numberOfColumns == 8)
		{
			return BIG_AIRPORT;
		}
		
		if (numberOfColumns == 6)
		{
			return SMALL_AIRPORT;
		}
		
		return 0;
	}
}